module(..., package.seeall)

require'zwsapi.app'

-----------------------------------------------------------------------------
-- Template for an error messages with a stack trace.
-----------------------------------------------------------------------------
local HTML_MESSAGE_WITH_STACK_TRACE = [[
<br/>
<span style="color:red; font-size: 19pt;">%s</span></br><br/><br/>
Error details: <pre><b><code>%s</code></b></pre><br/>
]]

-----------------------------------------------------------------------------
-- Template for an error messages without a stack trace.
-----------------------------------------------------------------------------
local HTML_MESSAGE_WITHOUT_STACK_TRACE = [[
<br/>
<span style="color:red; font-size: 19pt;">%s</span></br><br/><br/>
(If you are the admin for this site, you can turn on stack trace display
 by setting SHOW_STACK_TRACE parameter to true.)
]]

-----------------------------------------------------------------------------
-- Template for the error messages when error handling failed...
-----------------------------------------------------------------------------
ULTIMATE_OOPS_MESSAGE = [[
 <br/><br/><br/><br/>
 <center><b>Something went terribly wrong...</b></center>
]]


-----------------------------------------------------------------------------
-- Given two apps, creates a new one that uses the second app as a backup for
-- the first. So, the first app gets called first. If it runs successfully,
-- it's output is returned. If it fails, the second app is called for error
-- handling.
-----------------------------------------------------------------------------
local function make_safer_app(app_function, error_app_function)
	return function(ngx, err)
		local ok, status, headers, fn = xpcall(
					function() return app_function(ngx, err) end,
					function(e) return error_app_function(ngx, e) end
				)
		return status, headers, fn
	end
end


function reqfromngx(ngx)
	local header=ngx.req.get_headers()
	local req={
		_ngx=ngx,
		GET		  = {},
		POST		 = {},
		method	   = ngx.var.request_method,
		path_info	= ngx.var.uri,
		query_string = ngx.var.query_string or '',
		doc_root     = ngx.var.document_root,
		app_path     = nil,--ngx.var.APP_PATH
		env={
			CONTENT_TYPE=ngx.var.content_type,
		}
	}
	setmetatable(req,{__newindex=function (t,k,v)
		if k=='redirect' then
			local res=t._res
			if not t.olduser and t.user then
				res:set_cookie(t.cookie_name, {value=t.user.."|"..(t.auth_token or ""), path="/"})
			end
			-- Check if the action logged out the user.
			if not t.user then
				res:set_cookie(t.cookie_name, {value="", path="/"})
			end
				
			t._ngx.redirect(v)
		else
			rawset(t,k,v)
		end
	end})
	local t=req.GET
	for k, v in pairs(ngx.req.get_uri_args()) do
		if type(v) == "table" then
			t[k]=v[#v]
		else
			t[k]=v
		end
	end
	t=req.POST
	ngx.req.read_body()
	for k, v in pairs(ngx.req.get_post_args()) do
		if type(v) == "table" then
			t[k]=v[#v]
		else
			t[k]=v
		end
	end
	req.params = {}
	setmetatable(req.params, { __index = function (tab, name)
		local var = req.GET[name] or req.POST[name]
		rawset(tab, name, var)
		return var
	end})
	req.cookies = {}
	local cookies=string.gsub(";"..(header["Cookie"] or '')..";",
            "%s*;%s*", ";")
	setmetatable(req.cookies, { __index = function (tab, name)
		name = name
		local pattern = ";"..name.."=(.-);"
		local cookie = string.match(cookies, pattern)
		cookie = ngx.unescape_uri(cookie or '')
		rawset(tab, name, cookie)
		return cookie
	end})
	return req
end


methods = {}
methods.__index = methods

function methods:write(...)
	self._ngx.print(...)
end

function methods:forward(url)
	ngx.redirect(url)
end

function methods:finish()
	self._ngx.exit()
end

local function optional (what, name)
  if name ~= nil and name ~= "" then
    return string.format("; %s=%s", what, name)
  else
    return ""
  end
end

local function make_cookie(ngx,name, value)
  local options = {}
  if type(value) == "table" then
    options = value
    value = value.value
  end
  local cookie = name .. "=" .. ngx.escape_uri(value)
  if options.expires then
    local t = date("!%A, %d-%b-%Y %H:%M:%S GMT", options.expires)
    cookie = cookie .. optional("expires", t)
  end
  cookie = cookie .. optional("path", options.path)
  cookie = cookie .. optional("domain", options.domain)
  cookie = cookie .. optional("secure", options.secure)
  return cookie
end

function methods:set_cookie(name, value)
  local cookie = self.headers["Set-Cookie"]
  if type(cookie) == "table" then
    table.insert(self.headers["Set-Cookie"], make_cookie(self._ngx,name, value))
  elseif type(cookie) == "string" then
    self.headers["Set-Cookie"] = { cookie, make_cookie(self._ngx,name, value) }
  else
    self.headers["Set-Cookie"] = make_cookie(self._ngx,name, value)
  end
end

function methods:delete_cookie(name, path)
  self:set_cookie(name, { value =  "xxx", expires = 1, path = path })
end

function methods:redirect(url)
	self._ngx.redirect(url)
end

function methods:content_type(type)
  self.headers["Content-Type"] = type
end

function resfromngx(ngx)
	if not ngx.header["Content-Type"] then
		ngx.header["Content-Type"] = "text/html"
	end
	return setmetatable({_ngx=ngx,headers = ngx.header}, methods)
end

-----------------------------------------------------------------------------
-- Creates an optimistic WSAPI app function for app. This function
-- assumes that everything will work out right. We'll have to wrap this with
-- make_safer_app() to make sure the errors get reported properly.
-----------------------------------------------------------------------------
local function make_basic_zapp(modnew,config)
	local modnew=modnew or zwsapi.app.new
	local my_zapp = modnew(config)
	if config.INIT_FUNCTION then
		config.INIT_FUNCTION(my_zapp)
	end
	return function (ngx)
		local _tm_start=os.clock()
		local req,res=reqfromngx(ngx), resfromngx(ngx)
		req._res=res
		req.ip = ngx.var.remote_addr
		my_zapp:WSHandleRequest(req,res)
		--xpcall(function ()my_zapp:WSHandleRequest(req,res)end,print)
		if ngx.header["Location"] then
			if ngx.status < 300 then
				ngx.status = 302
			end
		end
		--print("cpu:"..(os.clock()-_tm_start),ngx.var.uri)
		return
	end
end


-----------------------------------------------------------------------------
-- An auxiliary functions to catch common errors and provide a better message
-- for them.
-----------------------------------------------------------------------------
local detect_common_errors = function(error_message, config)
	return
end


-----------------------------------------------------------------------------
-- Creates a WSAPI app for handling errors. This function tried to log the
-- error to the logger supplied in configuration. It then checks if it should
-- be displaying the stack trace to the user or not, and displays it if
-- appropriate.
-----------------------------------------------------------------------------
local function make_error_handling_app(config)
	return function(ngx, err)
		-- Try to log this error.
		--local ok, logger = copcall(zdc.logger.make_logger, config.LOGGER,
		--									config.LOGGER_PARAMS, config.LOGGER_LEVEL)
		--if ok then
		--	logger:error(err)
		--end

		-- Figure out what to tell the user.
		local summary = detect_common_errors(err, config)
							 or "ZNGINXAPI ran but failed due to an unexpected error."
		local message
		if config.sbenv.SHOW_STACK_TRACE then
			message = string.format(HTML_MESSAGE_WITH_STACK_TRACE, summary, err)
		else
			message = string.format(HTML_MESSAGE_WITHOUT_STACK_TRACE, summary)
		end
		--ngx.status = ngx.HTTP_GONE
		ngx.say(message)
		-- to cause quit the whole request rather than the current phase handler
		ngx.exit(500)

		return
	end
end


-----------------------------------------------------------------------------
-- Creates a WSAPI app for handling he ultimate FAIL: error in error handling.
--
-- This app gets called when the normal run failed, and then error
-- handling failed as well (presumably because the configuration is really
-- wrong). So, at this point it's just a matter of telling the user that
-- something went terribly wrong.
-----------------------------------------------------------------------------
local function make_oops_app()
	return function(ngx)
		--ngx.status = ngx.HTTP_GONE
		ngx.say(ULTIMATE_OOPS_MESSAGE)
		-- to cause quit the whole request rather than the current phase handler
		ngx.exit(500)
		return
	end
end

-----------------------------------------------------------------------------
-- Creates a WSAPI app function to handle requests based on a configuration
-- table. This is the only function exported by the module. The app returned
-- by this function is _safe_ in the sense that errors do not get
-- reported to WSAPI but are instead handled by a cascade of error-handling
-- apps.
--
-- @param config			a bootstrap configuration for app.
-----------------------------------------------------------------------------
function new(modnew,config)
	-- create three WSAPI app functions: one that we _want_ to run, another as
	-- a back up for the first (to report errors), and the third as a backup
	-- for the second (to deal with errors in error handling).
	local main_app = make_basic_zapp(modnew,config)
	local error_app = make_error_handling_app(config)
	local total_fail_app = make_oops_app()
	local f=make_safer_app(main_app, error_app) --make_safer_app(error_app, total_fail_app))
	return f
end

